﻿import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { CheckCircleIcon, PaperAirplaneIcon, SparklesIcon, WrenchIcon } from '@heroicons/react/24/outline'
import ReactMarkdown from 'react-markdown'

import { AIAPI } from '../app/api'
import type { ApiChatMessage } from '../app/types'
import PageHeader from '../components/PageHeader'

type ConversationMessage = {
  role: 'assistant' | 'user'
  text: string
}

type ToolEvent = {
  label: string
  status: 'running' | 'done'
}

const AssistantPage = () => {
  if (typeof document !== 'undefined') document.title = 'AI 助手 · Love Notes'
  const [messages, setMessages] = useState<ConversationMessage[]>([])
  const [summary, setSummary] = useState<string | null>(null)
  const [historyLoaded, setHistoryLoaded] = useState(false)
  const [historyError, setHistoryError] = useState<string | null>(null)
  const [isHistoryLoading, setIsHistoryLoading] = useState(false)
  const [hasMoreHistory, setHasMoreHistory] = useState(false)
  const [nextOffset, setNextOffset] = useState<number | null>(null)
  const [toolEvents, setToolEvents] = useState<ToolEvent[]>([])
  const [proposals, setProposals] = useState<Record<string, Record<string, number>> | null>(null)
  const [input, setInput] = useState('')
  const [threadId, setThreadId] = useState<string | null>(null)
  const [renderNonce, setRenderNonce] = useState(0)
  const [isStreaming, setIsStreaming] = useState(false)
  const controllerRef = useRef<AbortController | null>(null)
  const toolClearTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
  const scrollContainerRef = useRef<HTMLDivElement | null>(null)
  const prevScrollHeightRef = useRef<number | null>(null)
  const isLoadingMoreRef = useRef(false)

  const mapMessages = useCallback((items: ApiChatMessage[]): ConversationMessage[] => {
    return items.map((message) => ({ role: message.role, text: message.content }))
  }, [])

  const handleToolEvent = useCallback(
    (raw: string | { label?: string; status?: string }) => {
      if (typeof raw !== 'string') {
        const baseLabel = (raw.label ?? '工具').trim() || '工具'
        const status: 'running' | 'done' = raw.status === 'done' ? 'done' : 'running'
        if (toolClearTimeoutRef.current) {
          clearTimeout(toolClearTimeoutRef.current)
          toolClearTimeoutRef.current = null
        }
        setToolEvents((prev) => {
          const existingIndex = prev.findIndex((event) => event.label === baseLabel)
          if (existingIndex >= 0) {
            const next = [...prev]
            next[existingIndex] = { label: baseLabel, status }
            return next
          }
          return [...prev, { label: baseLabel, status }]
        })
        return
      }

      const inner = raw.replace(/^<love-tool>/, '').replace(/<\/love-tool>$/, '').trim()
      if (!inner) return
      const normalized = inner.replace(/…$/, '').trim()
      const isDone = normalized.endsWith('完成')
      const baseLabel = (isDone ? normalized.slice(0, -2) : normalized.replace(/^正在/, '')).trim() || '工具'
      const status: 'running' | 'done' = isDone ? 'done' : 'running'
      if (toolClearTimeoutRef.current) {
        clearTimeout(toolClearTimeoutRef.current)
        toolClearTimeoutRef.current = null
      }
      setToolEvents((prev) => {
        const existingIndex = prev.findIndex((event) => event.label === baseLabel)
        if (existingIndex >= 0) {
          const next = [...prev]
          next[existingIndex] = { label: baseLabel, status }
          return next
        }
        return [...prev, { label: baseLabel, status }]
      })
    },
    [toolClearTimeoutRef],
  )

  const scrollToBottom = useCallback(
    (behavior: ScrollBehavior = 'smooth') => {
      const node = scrollContainerRef.current
      if (!node) return
      node.scrollTo({ top: node.scrollHeight, behavior })
    },
    [],
  )

  const loadHistory = useCallback(
    async (options?: { offset?: number | null; thread?: string | null }) => {
      isLoadingMoreRef.current = Boolean(options?.offset && options.offset > 0)
      setIsHistoryLoading(true)
      setHistoryError(null)
      try {
        if (options?.offset && options.offset > 0) {
          const node = scrollContainerRef.current
          if (node) prevScrollHeightRef.current = node.scrollHeight
        }
        const response = await AIAPI.fetchHistory({
          thread_id: options?.thread ?? threadId ?? undefined,
          offset: options?.offset ?? undefined,
          limit: 20,
        })
        if (response.thread_id && response.thread_id !== threadId) {
          setThreadId(response.thread_id)
        }

        const incoming = mapMessages(response.messages)

        if (options?.offset && options.offset > 0) {
          setMessages((prev) => [...incoming, ...prev])
          // 维护滚动位置，避免加载更多时跳动
          setTimeout(() => {
            const node = scrollContainerRef.current
            if (node && prevScrollHeightRef.current !== null) {
              const delta = node.scrollHeight - prevScrollHeightRef.current
              node.scrollTop = Math.max(delta, 0)
              prevScrollHeightRef.current = null
            }
          }, 0)
        } else {
          setMessages(incoming)
        }

        setSummary(response.summary ?? null)
        setHasMoreHistory(response.has_more)
        setNextOffset(response.next_offset ?? null)
      } catch (error) {
        const message = error instanceof Error ? error.message : '历史加载失败，请稍后重试。'
        setHistoryError(message)
      } finally {
        setIsHistoryLoading(false)
        setHistoryLoaded(true)
      }
    },
    [mapMessages, threadId],
  )

  useEffect(() => {
    let mounted = true
    ;(async () => {
      if (!mounted) return
      await loadHistory()
      try {
        const res = await AIAPI.getMemoryProposals()
        setProposals(res.proposals || null)
      } catch {}
    })()
    return () => {
      mounted = false
      if (toolClearTimeoutRef.current) {
        clearTimeout(toolClearTimeoutRef.current)
      }
    }
  }, [loadHistory])

  useEffect(() => {
    if (historyLoaded) {
      scrollToBottom('auto')
    }
  }, [historyLoaded, scrollToBottom])

  useEffect(() => {
    if (!historyLoaded) {
      return
    }
    if (isLoadingMoreRef.current) {
      isLoadingMoreRef.current = false
      return
    }
    scrollToBottom(isStreaming ? 'smooth' : 'auto')
  }, [messages, historyLoaded, isStreaming, scrollToBottom])

  const startConversation = async () => {
    if (!input.trim() || isStreaming) return

    const userMessage: ConversationMessage = { role: 'user', text: input.trim() }
    setMessages((prev) => [...prev, userMessage])
    setInput('')

    if (toolClearTimeoutRef.current) {
      clearTimeout(toolClearTimeoutRef.current)
      toolClearTimeoutRef.current = null
    }
    setToolEvents([])

    const abortController = new AbortController()
    controllerRef.current = abortController
    setIsStreaming(true)
    setHistoryError(null)

    try {
      const currentThread = threadId ?? crypto.randomUUID()
      const response = await AIAPI.streamChat(
        {
          thread_id: currentThread,
          message: userMessage.text,
        },
        { signal: abortController.signal },
      )

      const headerThread = response.headers.get('X-Thread-Id')
      if (headerThread && headerThread !== threadId) {
        setThreadId(headerThread)
      } else if (!threadId) {
        setThreadId(currentThread)
      }

      if (!response.body) {
        throw new Error('无法建立流式连接')
      }

      const reader = response.body.getReader()
      const decoder = new TextDecoder('utf-8')
      let done = false
      let eventBuffer = ''

      const pushChunk = (chunk: string) => {
        if (!chunk) return
        // 提升流式可读性：单换行 -> Markdown 段落换行，并清理工具标签残留
        let normalized = chunk
          .replace(/\r?\n/g, "\n\n")
          .replace(/<\/love-tool>/g, '')
          .replace(/<love-tool>/g, '')
        // 将裸链接包裹为 Markdown 自动链接，便于点击
        let linkified = normalized.replace(/(?<!\(|<)(https?:\/\/[^\s)]+)/g, '<$1>')
        // 将“： ### 标题/。 ### 标题”等内联标题转为换行标题，提升流式渲染效果
        linkified = linkified.replace(/([：:。！？!?])\s*(#{1,6}\s)/g, '\n\n$2')
        setMessages((prev) => {
          const updated = [...prev]
          const last = updated[updated.length - 1]
          // 若分片以 Markdown 区块起始标记开头，而上一个消息末尾没有空行，补空行
          const startsBlock = /^(\s*)(#{1,6}\s|\*\s|-\s|\d+\.\s|```|>\s)/.test(linkified.trimStart())
          if (last && last.role === 'assistant' && startsBlock && !last.text.endsWith('\n\n')) {
            linkified = '\n\n' + linkified
          }
          if (!last || last.role !== 'assistant') {
            updated.push({ role: 'assistant', text: linkified })
          } else {
            updated[updated.length - 1] = { role: 'assistant', text: last.text + linkified }
          }
          return updated
        })
        requestAnimationFrame(() => scrollToBottom('auto'))
      }

      const processPayload = (payloadText: string) => {
        if (!payloadText) return
        if (payloadText.includes('<love-tool>')) {
          const stripped = payloadText.replace(/<\/?love-tool>/g, '')
          if (stripped.trim()) {
            pushChunk(stripped)
          }
          return
        }
        pushChunk(payloadText)
      }

      while (!done) {
        const { value, done: readerDone } = await reader.read()
        done = readerDone
        if (value) {
          eventBuffer += decoder.decode(value, { stream: !done })
          let separatorIndex = eventBuffer.indexOf('\n\n')
          while (separatorIndex !== -1) {
            const rawEvent = eventBuffer.slice(0, separatorIndex)
            eventBuffer = eventBuffer.slice(separatorIndex + 2)
            separatorIndex = eventBuffer.indexOf('\n\n')
            if (!rawEvent.startsWith('data:')) continue
            const payloadRaw = rawEvent.slice(5)
            const payloadNormalized = payloadRaw.trim()
            if (!payloadNormalized) continue
            if (payloadNormalized === '[DONE]') {
              done = true
              break
            }
            try {
              const parsed = JSON.parse(payloadNormalized)
              if (parsed && typeof parsed === 'object' && parsed.type === 'tool') {
                handleToolEvent(parsed)
                continue
              }
            } catch {
              // 非 JSON 负载，继续按文本处理
            }
            if (payloadNormalized.startsWith('[ERROR]')) {
              pushChunk('助手暂时不可用，请稍后重试。')
              done = true
              break
            }
            processPayload(payloadRaw)
          }
        }
      }
    } catch (error) {
      setMessages((prev) => [...prev, { role: 'assistant', text: '助手暂时不可用，请稍后重试。' }])
      setHistoryError(error instanceof Error ? error.message : null)
    } finally {
      setIsStreaming(false)
      controllerRef.current = null
      toolClearTimeoutRef.current = setTimeout(() => {
        setToolEvents([])
        toolClearTimeoutRef.current = null
      }, 1200)
      // 流式结束后，兜底清理助手最后一条中的工具标签，并做一次 Markdown 语义修正（确保区块起始换行）
      setMessages((prev) => {
        if (!prev.length) return prev
        const last = prev[prev.length - 1]
        if (last.role !== 'assistant') return prev
        let cleaned = last.text.replace(/<\/?love-tool>/g, '')
        // 将“： ###”这类内联标题修正为换行标题
        cleaned = cleaned.replace(/：\s*(#{2,6}\s)/g, '\n\n$1')
        // 合并多余空行为最多两个
        cleaned = cleaned.replace(/\n{3,}/g, '\n\n')
        if (cleaned === last.text) return prev
        const next = [...prev]
        next[next.length - 1] = { ...last, text: cleaned }
        return next
      })
      // 触发一次局部刷新，确保 Markdown 重新渲染
      setRenderNonce((n) => n + 1)
    }
  }

  const handleLoadMore = async () => {
    if (!hasMoreHistory || isHistoryLoading || nextOffset === null) return
    await loadHistory({ offset: nextOffset, thread: threadId })
  }

  const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {
    const node = event.currentTarget
    if (node.scrollTop <= 8 && hasMoreHistory && !isHistoryLoading && nextOffset !== null) {
      void handleLoadMore()
    }
  }

  const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault()
    void startConversation()
  }

  const assistantMessages = useMemo(() => messages, [messages])

  const visibleToolEvents = useMemo(() => {
    if (toolEvents.length === 0) return []
    const active = toolEvents.filter((event) => event.status === 'running')
    const done = toolEvents.filter((event) => event.status === 'done')
    return [...active, ...done]
  }, [toolEvents])

  const toolBanner = visibleToolEvents.length ? (
    <div className="mb-3 space-y-2">
      {visibleToolEvents.map((event, index) => (
        <div
          key={`${event.label}-${index}`}
          className={`tool-banner ${event.status === 'done' ? 'tool-banner--done' : 'tool-banner--running'}`}
        >
          {event.status === 'done' ? (
            <CheckCircleIcon className="tool-banner-icon" />
          ) : (
            <WrenchIcon className="tool-banner-icon" />
          )}
          <span>{event.status === 'done' ? `${event.label}完成` : `${event.label}处理中…`}</span>
        </div>
      ))}
    </div>
  ) : null

  const MemoryProposalCard = proposals ? (
    <div className="mb-3 glass-panel p-4 text-sm">
      <div className="mb-2 flex items-center justify-between">
        <div className="font-semibold">发现可能的重要记忆，是否保存？</div>
        <button
          className="rounded bg-amber-500 px-2 py-1 text-xs text-white"
          onClick={async () => {
            await AIAPI.clearMemoryProposals()
            setProposals(null)
          }}
        >忽略本次</button>
      </div>
      <div className="grid gap-2 sm:grid-cols-2">
        {Object.entries(proposals).map(([k, bucket]) => (
          Object.keys(bucket).length ? (
            <div key={k} className="rounded-lg border border-white/15 bg-white/10 p-2">
              <div className="mb-1 text-xs opacity-75">{k}</div>
              <div className="flex flex-wrap gap-2">
                {Object.entries(bucket).map(([item, count]) => (
                  <label key={item} className="inline-flex items-center gap-1 rounded border border-white/20 bg-white/10 px-2 py-0.5 text-xs">
                    <input type="checkbox" defaultChecked />
                    <span className="opacity-90">{item}</span>
                    <span className="text-[10px] opacity-70">×{count}</span>
                  </label>
                ))}
              </div>
            </div>
          ) : null
        ))}
      </div>
      <div className="mt-3 text-right">
        <button
          className="rounded bg-emerald-500 px-3 py-1.5 text-xs font-semibold text-white"
          onClick={async (e) => {
            // 简化：先提交全部提案项（后续可细化选择）
            const payload: any = {}
            for (const [k, bucket] of Object.entries(proposals)) {
              payload[k] = Object.keys(bucket)
            }
            await AIAPI.commitMemoryProposals(payload)
            setProposals(null)
          }}
        >保存到记忆</button>
      </div>
    </div>
  ) : null

  return (
    <div className="space-y-4">
      <PageHeader
        eyebrow="AI Companion"
        title="Love Notes AI 情感伙伴"
        description="和小蛋对话，获得贴心、具体的建议。"
      />
      <section className="glass-panel flex h-[72vh] flex-col bg-white/95 p-6 shadow-card lg:h-[76vh]">
        <header className="mb-4 flex items-center gap-2">
          <span className="inline-flex items-center justify-center rounded-full bg-blush-500/10 p-2 text-blush-500">
            <SparklesIcon className="h-5 w-5" />
          </span>
          <div>
            <h2 className="text-lg font-semibold text-slate-900">Love Notes AI 助手</h2>
            <p className="text-xs text-slate-500">实时分析日记、纪念日、生理期记录，为双方提供下一步建议。</p>
          </div>
        </header>
        {summary ? (
          <div className="mb-3 glass-panel p-4 text-sm">
            <p className="mb-2 text-xs font-semibold uppercase tracking-widest text-slate-400">会话摘要</p>
            <ReactMarkdown
              className="markdown-body"
              components={{
                a: (props) => (
                  <a {...props} target="_blank" rel="noopener noreferrer" className="text-blush-600 underline" />
                ),
              }}
            >
              {summary}
            </ReactMarkdown>
          </div>
        ) : null}
        {toolBanner}
        <div
          ref={scrollContainerRef}
          onScroll={handleScroll}
          className="flex-1 overflow-y-auto space-y-4 rounded-2xl bg-slate-50/70 p-4"
        >
          {hasMoreHistory ? (
            <div className="sticky top-0 z-10 mb-2 text-center text-[11px] text-slate-400">
              {isHistoryLoading ? '正在加载更早的记录…' : '上滑加载更早的记录'}
            </div>
          ) : null}
          <div className="sticky top-4 z-10 mb-2 flex gap-2 overflow-x-auto py-1">
            {['回顾最近 3 篇日记', '下周是否有纪念日', '根据偏好推荐约会', '看看最近情绪趋势'].map((action) => (
              <button
                key={action}
                onClick={() => setInput(action)}
                className="shrink-0 rounded-full border border-slate-200 bg-white px-3 py-1 text-[11px] text-slate-600 hover:border-blush-200 hover:text-blush-500 disabled:opacity-60"
                disabled={isStreaming}
                type="button"
              >
                {action}
              </button>
            ))}
          </div>
          {!historyLoaded && isHistoryLoading ? (
            <p className="text-sm text-slate-400">对话记录加载中...</p>
          ) : assistantMessages.length === 0 ? (
            <p className="text-sm text-slate-400">
              {historyError ? historyError : '还没有对话，发起第一次提问吧。'}
            </p>
          ) : (
            assistantMessages.map((message, index) => (
              <div
                key={`${renderNonce}-${index}-${message.role}-${message.text.length}`}
                className={`flex ${message.role === 'assistant' ? 'justify-start' : 'justify-end'} px-1`}
              >
                <div
                  className={`relative max-w-[80%] rounded-2xl px-4 py-3 text-sm leading-relaxed shadow-sm ${
                    message.role === 'assistant'
                      ? 'border border-white/15 bg-white/10 text-slate-100'
                      : 'bg-gradient-to-br from-blush-500 to-rose-500 text-white shadow-card'
                  }`}
                >
                  {/* 移除三点菜单 */}
                  {message.role === 'assistant' ? (
                    <ReactMarkdown
                      className="markdown-body"
                      components={{
                        a: (props) => (
                          <a {...props} target="_blank" rel="noopener noreferrer" className="text-blush-600 underline" />
                        ),
                      }}
                    >
                      {message.text}
                    </ReactMarkdown>
                  ) : (
                    message.text
                  )}
                </div>
              </div>
            ))
          )}
        </div>
        <form onSubmit={handleSubmit} className="mt-4 input-shell flex items-center gap-3 px-4 py-3">
          <input
            value={input}
            onChange={(event) => setInput(event.target.value)}
            placeholder="向 AI 提问，例如：结合最近的日记给出沟通建议"
            className="flex-1 bg-transparent text-sm text-[color:var(--text-secondary)] placeholder:text-[color:var(--text-tertiary)] outline-none"
            disabled={isStreaming}
          />
          <button
            type="submit"
            className="inline-flex items-center gap-2 rounded-full bg-blush-500 px-4 py-2 text-xs font-semibold text-white hover:bg-blush-600 disabled:cursor-not-allowed disabled:opacity-60"
            disabled={isStreaming}
          >
            <PaperAirplaneIcon className="h-4 w-4" />
            发送
          </button>
        </form>
      </section>
    </div>
  )
}

export default AssistantPage
